#pragma once

#include <functional>

#include "base.h"
#include "../impl/minmax.h"

namespace mdtx::online::updater
{
    template <typename T>
    class minmax : public updater_unary2<T>,
                   public impl::minmax<T>
    {
        using base = updater_unary2<T>;
        using ibase = impl::minmax<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->mindeq.front(), this->maxdeq.front());
        }
    };

    template <typename T>
    class highlow : public updater_binary2<T>,
                    public impl::minmax<T>
    {
        using base = updater_binary2<T>;
        using ibase = impl::minmax<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in), std::get<1>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in), std::get<1>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->maxdeq.front(), this->mindeq.front());
        }
    };

    template <typename T>
    class min : public updater_unary1<T>,
                public impl::extrema<T, std::less<T>>
    {
        using base = updater_unary1<T>;
        using ibase = impl::extrema<T, std::less<T>>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->deq.front());
        }
    };

    template <typename T>
    class max : public updater_unary1<T>,
                public impl::extrema<T, std::greater<T>>
    {
        using base = updater_unary1<T>;
        using ibase = impl::extrema<T, std::greater<T>>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->deq.front());
        }
    };

    template <typename T>
    class argminmax : public counter_base2<T>,
                      public impl::argminmax<T>
    {
        using base = counter_base2<T>;
        using ibase = impl::argminmax<T>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->minidx.front(), this->maxidx.front());
        }
    };

    template <typename T>
    class argmin : public counter_base1<T>,
                   public impl::argextrema<T, std::less<T>>
    {
        using base = counter_base1<T>;
        using ibase = impl::argextrema<T, std::less<T>>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->ideq.front());
        }
    };

    template <typename T>
    class argmax : public counter_base1<T>,
                   public impl::argextrema<T, std::greater<T>>
    {
        using base = counter_base1<T>;
        using ibase = impl::argextrema<T, std::greater<T>>;

    public:
        using typename base::tuple_in;
        using typename base::tuple_out;
        using base::insert;
        using base::remove;

        using ibase::insert_impl;
        using ibase::remove_impl;
        using ibase::reset_impl;

        void insert(tuple_in const &in) override
        {
            insert_impl(std::get<0>(in));
        }

        void remove(tuple_in const &in) override
        {
            remove_impl(std::get<0>(in));
        }

        void reset() override
        {
            reset_impl();
        }

        tuple_out get() override
        {
            return tuple_out(this->ideq.front());
        }
    };
}
